//@include lib_Class.js
//@include lib_sol.common.Config.js
//@include lib_sol.common.TranslateTerms.js
//@include lib_sol.common.ObjectUtils.js
//@include lib_sol.common.SordUtils.js
//@include lib_sol.common.Template.js
//@include lib_sol.common.WfUtils.js
/*
* @author ESt, ELO Digital Office GmbH
* @version 1.03.000
*
* @elojc
* @eloas
* @eloix
*
* Injection Mixin
*
* You can use this mixin in business solution classes to load configurations
* and apply templating to them using sord- or other data
*
* As this mixin calls the including classes' inject method, you have to define an inject
* method in the including class.
*
* Usually, this mixin is accompanied by a solution-specific `configuration`-mixin, which
* is responsible for describing the relationship between a config-name and the config repository path.
* (see sol.hr.mixins.Configuration for an example configuration-description file)
*
* A typical usage of this mixin would be e.g. an indexserver-service.
* ## Possible Injections
* Injections have to be defined in the classes `inject`-property.
*
* ### Config Injection
*
* You can inject configs or a property of a config by using the `config` and `prop` keywords:
*
* { config: "hr", prop: "entities.file.valueA", template: true }
*
* This would initialize the hr.config, read the property `valueA` and apply templating to it.
* The `template` keyword is optional. You can enable it, if the config property contains a handlebars template to render it.
* You may also define `emptyNonRendered: true`, which will set the handlebars template to an empty string, if it could not be rendered.
* (Otherwise the handlebars template will still be the value of the injected string)
*
* Every injected config property injection which results in an object or array will be deep-copied.
*
* ### Sord Injection
*
* Injecting sords is possible by using the `sordId` and `flowId` keywords:
*
* { sordId: "12345", flowId: "442381" }
*
* Usually the objId and flowId are values one does not know at compile time and must therefore be determined dynamically.
* You can use
*
* { sordIdFromProp: "objId", flowIdFromProp: "flowId" }
*
* You can use the optional setting to skip injecting a sord if no objId was found:
*
* { sordIdFromProp: "objId", flowIdFromProp: "flowId", optional: true }
*
* to access a property stored in `me`/`this` (see extensive example below)
*
* If you define the property `includeBlobs`, the sord's FormBlobs will also we read.
*
* All sords will automatically be added to the data used for handlebars templating.
* If you don't want to add it to the templating data, you can define the property `forTemplating` as false.
*
* ### JSON Injection
*
* You can inject a json-string, which will then be parsed, by using the `json` property.
*
* { json: '{ "myProp": 12345123 }' }
*
* As with sordIdFromProp, you can use `jsonFromProp` to read the JSON from a variable.
*
* All parsed JSON will be added to the data used for handlebars templating.
* If you don't want to add it to templating, set the `forTemplating` property to false.
*
* ### Property Injection (Inject into templating data)
*
* You can add any data local to the class (`me`/`this`) to the handlebars templating by defining
*
* { prop: "params" }
*
* Usually, the injection mechanism would overwrite the original params if you define
*
* params: { prop: "params" }
*
* ### Add to templating but don't inject
*
* You can disable injection by defining the property `dontInject` as true.
* This way, the property will be added to templating but won't be injected.
* This is only possible in Sord-, JSON- and Property-Injection.
*
* ### Disable logging of an injected value
*
* You can disable the logging of specific injections by defining the property
* `log` as false. This may be useful for sensitive data. Instead of the value,
* `N/A` will be shown in the logs.
* Always doublecheck if the actual logging outputs reflect your wishes.
*
* ### Usage of `sol.common.mixins.Injector.SordToken`
*
* Use this token mixin to inject automatically sord injectorId by objId or source prop.
* The mixin function will append either `{sord: { "sordIdFromProp": "objId"}}` or `{sord: { "prop": "source"}}`
* to your `inject: {}` variable. It depends which params you have defined in your function context. me.objId will always
* win when it is set. Otherwise it will use me.source to create the sordToken.
* The token will only append when token (injectorId) is not already set.
*
* sol.define("sol.hr.ix.actions.GetMyServiceResult", {
* extend: "sol.common.ix.ServiceBase",
*
* mixins: ["sol.hr.mixins.Configuration", "sol.common.mixins.Injector.SordToken", "sol.common.mixins.Injector.SordToken"],
* }
*
*
* It is important that `sol.common.mixins.Injector.SordToken` is included first before `sol.common.mixins.Injector.SordToken` mechanismus is triggered!
* It does not matter if the inject mechanism is called manually.
*
* ## Example
*
* This simple example reads a parameter from the config-file, applies a sord, a parameter and a translation to the template
* and returns the value. Note: sords are retrieved with a normal user connection. If you need access to a sord via ixConnectAdmin,
* you must checkout the sord manually in the initialize function and add it to the templating using the `prop`-option
*
* /hr/Configuration/hr.config:
* {
* entities: { file: { valueA: "{{translate 'sol.hr.descr'}}{{PERSONNELFILE.objKeys.LASTNAME}}, {{params.name}}" } }
* }
*
* /hr/All Rhino/lib_sol.hr.mixins.Configuration:
* //include lib_Class.js
*
* sol.define("sol.hr.mixins.Configuration", {
* mixin: true,
*
* $configRelation: {
* hr: "/hr/Configuration/hr.config",
* myOtherConfig: "/hr/Configuration/something.config",
* // load recruiting.config. if it is not found, load recruiting.fallback.config
* recruiting_in_hr: ["/recruiting/Configuration/recruiting.config", "/hr/Configuration/recruiting.fallback.config"]
* }
* });
*
* note: this example implies that the parameters
* { objId: "12345", flowId: "441234", name: "Vertragsanpassung" }
* are passed to the service.
* //include lib_sol.hr.mixins.Configuration.js
* //include lib_sol.common.Injection.js
*
* sol.define("sol.hr.ix.actions.GetMyServiceResult", {
* extend: "sol.common.ix.ServiceBase",
*
* mixins: ["sol.hr.mixins.Configuration", "sol.common.mixins.Inject"],
*
* inject: {
* myConfigValue: { config: "hr", prop: "entities.file.valueA", template: true }, // ""
* // templating data
* PERSONNELFILE: { sordIdFromProp: "objId", flowIdFromProp: "flowId" },
* params: { prop: "params" }
* // mySpecialSord: { prop: "myAdminAccessSord" }
* },
*
* initialize: function (params) {
* var me = this;
* this.$super("sol.common.ix.ActionBase", "initialize", [params]);
* me.params = params;
* // me.myAdminAccessSord = me.getMySordViaAdminConnection();
* },
*
* process: function () {
* var me = this;
* return me.myConfigValue; // "Titel: Mayer, Vertragsanpassung"
* }
*
* ## initializing config before initialize of calling class
*
* If you need access to config values during initialize(), you can omit "sol.common.mixins.Inject" from the mixins array
* and call its constructor manually:
*
* mixins: ["sol.hr.mixins.Configuration"],
* initialize: function (params) {
* var me = this;
* sol.create("sol.common.Injection").inject(me); // sets up config including templating ...
*
* me.tableTitle = me.dynkwl.tableTitle; // this is a config value we need before calling $super.initialize();
* me.$super("sol.hr.ix.dynkwl.MyIterator", "initialize", [config]);
* }
*
*/
sol.define("sol.common.mixins.Inject", {
mixin: true,
initialize: function () {
var me = this;
me.logger && me.logger.debug(["Initializing injection mixin."]);
sol.create("sol.common.Injection").inject(me);
}
});
sol.define("sol.common.mixins.Injection.SordToken", {
mixin: true,
initialize: function (config) {
var me = this, sordInjector;
sordInjector = config.objId
? { sord: { sordIdFromProp: "objId" } }
: { sord: { prop: "source" } };
// We can pass source as templateSord directly to the function
// in this case we dont want to checkout the sord again
// so we provide the templateSord object as sord to handlebar.
// That means we can use either objId or templateSord object to inject the
// sord object to the handlebar context and resolve templates in configs
sol.common.InjectionUtils.addInjections(me, sordInjector);
}
});
sol.define("sol.common.Injection", {
/**
* @private
* used in logging messages if a string if masked with ifLog() (injection.log===false)
*/
noLogTxt: "N/A",
/**
* returns `any` if `log` is not false. Otherwise returns `me.noLogTxt`.
*/
ifLog: function (log, any) {
var me = this;
return (log !== false) ? any : me.noLogTxt;
},
/**
* retrieves template sord of objId/guid & flowId
* make sure the objId/guid exists first!
* @param {String} objId GUID/ObjId of sord to perform checkout on
* @param {String} flowId used by getTemplateSord to retrieve wfMapKeys and formblobs
* @return {TemplateSord} returns the requested templateSord
*/
getSordData: function (objId, flowId, asAdmin, formBlobs) {
var me = this;
me.logger.debug(["Retrieving sord-data"]);
return sol.common.WfUtils.getTemplateSord(
((asAdmin && typeof ixConnectAdmin !== "undefined") ? ixConnectAdmin : ixConnect).ix().checkoutSord(objId, SordC.mbAllIndex, LockC.NO),
flowId || me.flowId,
{ asAdmin: asAdmin, formBlobs: formBlobs }
).sord;
},
initConfig: function (configName, classContext) {
var me = this, configPaths, configLoaded;
me.logger.debug(["Reading configuration because injection requires access to configuration `{0}`. See further log for the actual config-property injection.", configName]);
if (me.typeOf(classContext.$configRelation, "object")) {
configPaths = classContext.$configRelation[configName];
if (configPaths && (Array.isArray(configPaths) || (configPaths = [configPaths]))) {
configLoaded = configPaths.some(function (path) {
try {
classContext.$configs[configName] = (sol.create("sol.common.Config", { compose: path, copy: true })).config;
} catch (_) {}
return !!classContext.$configs[configName];
});
if (!configLoaded) {
throw "Config at path " + configPaths[0] + "was not found!";
}
me.logger.debug(["Configuration `{0}` loaded.", configName]);
} else {
throw "$configRelation of configuration-mixin did not contain a configuration-path for config `" + configName + "`.";
}
} else {
throw "$configRelation is not defined or not a javascript object. Please make sure to include a configuration mixin in the calling class.";
}
},
injectConfig: function (config, injectId, classContext) {
var me = this, configName = String(config.config);
me.logger.debug(["Setup configuration `{0}` for injection `{1}`.", configName, injectId]);
classContext.$configs = classContext.$configs || {};
classContext.$configs[configName] || me.initConfig(configName, classContext);
me.logger.debug("Adding config-property injection object to backlog for subsequent injection", me.ifLog(config.log, config));
me.$configInjections.push(config);
},
injectSordById: function (sordConfig, injectId, classContext) {
var me = this, sordData,
sordId = sordConfig.sordId || sol.common.ObjectUtils.getProp(classContext, sordConfig.sordIdFromProp),
flowId = sordConfig.flowId || sol.common.ObjectUtils.getProp(classContext, sordConfig.flowIdFromProp);
if (!sordId && sordConfig.optional) {
me.logger.debug("Optional sord was not found and therefore not injected. Continuing ...");
return;
}
me.logger.debug(["Injecting sord-data of injection `{0}` by id `{1}` using flowId `{2}`.", injectId, me.ifLog(sordConfig.log, sordId), me.ifLog(sordConfig.log, flowId || me.flowId)]);
sordData = me.getSordData(sordId, flowId, false, (sordConfig.includeBlobs === true));
if (sordConfig.forTemplating !== false) { // is added to templating as default
me.logger.debug("Adding retrieved sord-data to templating-data.");
classContext.$templatingData = classContext.$templatingData || {};
classContext.$templatingData[injectId] = sordData; // also add sord to templating
} else {
me.logger.debug("Sord-data has been retrieved but has not been added to templating-data.");
}
return sordConfig.dontInject ? undefined : sordData;
},
injectJSON: function (json, injectId, classContext) {
var me = this, result;
me.logger.debug(["Parsing JSON of injection `{0}`.", injectId]);
result = JSON.parse(me.typeOf(json.json, "string") ? String(json.json) : sol.common.ObjectUtils.getProp(classContext, json.jsonFromProp));
if (json.forTemplating !== false) { // is added to templating as default
me.logger.debug("Adding parsed JSON to templating-data.");
classContext.$templatingData = classContext.$templatingData || {};
classContext.$templatingData[injectId] = result; // also add data to templating
} else {
me.logger.debug("JSON has been parsed but has not been added to templating-data.");
}
return json.dontInject ? undefined : result;
},
injectFromThis: function (prop, injectId, classContext) {
var me = this, result, propType;
me.logger.debug(["Reading property of class-context as defined in injection `{0}`.", injectId]);
result = sol.common.ObjectUtils.getProp(classContext, String(prop.prop));
propType = me.typeOf(result);
me.logger.debug("Property value read", me.ifLog(prop.log, result));
if ((propType === "object") || (propType === "array")) {
me.logger.debug("The value is an object or an array. It will be cloned to minimize sideeffects");
result = me.copyConfig(result);
}
if (prop.forTemplating !== false) { // is added to templating as default
me.logger.debug("Adding value to templating-data");
classContext.$templatingData = classContext.$templatingData || {};
classContext.$templatingData[injectId] = result; // also add data to templating
} else {
me.logger.debug("Property has been read but has not been added to templating-data.");
}
return prop.dontInject ? undefined : result;
},
injectConfigProperty: function (config, classContext) {
var me = this, prop, propType;
me.logger.debug(["Reading config-property `{0}` from configuration `{1}`.", config.prop, config.config]);
prop = sol.common.ObjectUtils.getProp(classContext.$configs[config.config], config.prop);
propType = me.typeOf(prop);
me.logger.debug("Configuration value read", me.ifLog(config.log, prop));
if ((propType === "object") || (propType === "array")) {
me.logger.debug("The value is an object or an array. It will be cloned to minimize sideeffects");
prop = me.copyConfig(prop);
}
return prop;
},
copyConfig: function (obj) {
return JSON.parse(JSON.stringify(obj, function (_, val) {
return (val && val.getClass) ? String(val) : val;
}));
},
performInjection: function (injection, injectionId, classContext) {
var me = this, injectionFct;
me.logger.debug(["Deciding on injection mechanism for injection `{0}`", injectionId]);
if ((me.typeOf(injection.config, "string") && String(injection.config)) && (me.typeOf(injection.prop, "string"))) {
injectionFct = me.injectConfig; // setup config
} else if ((injection.sordId || injection.sordIdFromProp) && (injection.sordId + "")) {
injectionFct = me.injectSordById; // injects a sord by passed Id
} else if (me.typeOf(injection.json, "string") || (me.typeOf(injection.jsonFromProp, "string") && String(injection.jsonFromProp))) {
injectionFct = me.injectJSON; // injects data parsed from JSON
} else if (me.typeOf(injection.prop, "string") && String(injection.prop)) {
injectionFct = me.injectFromThis; // injects data from this to this (basically only used to expose data to templating)
} else {
throw "Injection `" + injectionId + "` did not match any injection mechanism or the mechanism configuration is incomplete. (possible mechanisms: config, sordId, json, prop, ...)";
}
injection.injectId = injectionId;
injection.template && me.$injectionsToTemplate.push(injection);
return injectionFct && injectionFct.call(me, injection, injectionId, classContext);
},
/**
* injects data into a class `classContext`
* @param {InitializedClassContext} classContext me/this of the class to inject data into.
*/
inject: function (classContext) {
var me = this, injections = classContext.inject, injectionResult;
me.$configInjections = [];
me.$injectionsToTemplate = [];
me.typeOf = sol.common.ObjectUtils.type;
me.logger.enter("sol.common.Injection.inject");
me.logger.debug("Running inject on class-context");
if (me.typeOf(injections, "object")) {
me.logger.debug("Analyzing injections", injections);
Object.keys(injections).forEach(function (injectionId) {
var injection = injections[injectionId];
me.logger.debug(["Testing injection `{0}`", injectionId]);
if (me.typeOf(injection, "object")) {
me.logger.debug(["Acting on injection `{0}`", injectionId]);
injectionResult = me.performInjection(injection, injectionId, classContext);
// every function will return its result which will then be injected. only config properties don't return & will be injected later
if (injectionResult !== undefined) {
classContext[injectionId] = injectionResult;
me.logger.debug(["Injection `{0}` has been injected into the class context. Value", injectionId], me.ifLog(injection.log, injectionResult));
} else {
me.logger.debug(["Injection value of injection `{0}` is undefined. This may be ok, if configuration was read or you defined `dontInject`", injectionId]);
}
} else {
throw "Injecting `" + injectionId + "` failed. The property value is not a javascript object.";
}
});
me.logger.debug("Inject config properties from backlog");
me.$configInjections.forEach(function (injection) {
classContext[injection.injectId] = me.injectConfigProperty(injection, classContext);
});
me.$injectionsToTemplate.forEach(function (injection) {
if (injection.template) {
me.logger.debug("Applying template to injected value. Value before templating", me.ifLog(injection.log, classContext[injection.injectId]));
classContext[injection.injectId] = sol.common.TemplateUtils.render(classContext[injection.injectId], classContext.$templatingData, { emptyNonRendered: !!injection.emptyNonRendered });
me.logger.debug("Value after templating", me.ifLog(injection.log, classContext[injection.injectId]));
}
});
me.logger.debug("All injections have been performed.");
} else {
throw "No injections defined. `inject` property value must be a javascript object. Type was: `" + me.typeOf(injections) + "`";
}
me.logger.exit("sol.common.Injection.inject");
}
});
sol.define("sol.common.InjectionUtils", {
singleton: true,
/**
* Append all injection keys to `context.inject` when the key does not already exist
* @param {*} context
* @param {*} injections
*/
addInjections: function (context, injections) {
Object.keys(injections)
.filter(function (key) {
return !context.inject[key];
})
.forEach(function (key) {
context.inject[key] = injections[key];
});
}
});